package sonnet.client;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.net.URLConnection;

import sonnet.exceptions.RemoteInvocationException;
import sonnet.interfaces.RemoteObject;

/**
 * Clients use this class to invoke remote methods
 *
 * @author Bozhidar Bozhanov
 */
public class RMClient {

	/**
	 * Use this method to invoke remote methods, by specifying their identifiers
	 *
	 * @param targetUrl the url of the server servlet
	 * @param className target class name (including package)
	 * @param methodName target method name
	 * @param args method arguments
	 * @return the result returned by the remote method. Should be cast to the expected type
	 * @throws RemoteInvocationException
	 */
	public static Serializable invoke(String targetUrl, String className, String methodName, Serializable[] args)
		throws RemoteInvocationException
	{
		URL url;
        ByteArrayOutputStream byteStream = null;
        ObjectInputStream is = null;
        ObjectOutputStream os = null;
        InputStream in = null;

        try {
            url = new URL(targetUrl);
            URLConnection conn = url.openConnection();

            byteStream = new ByteArrayOutputStream();
            os = new ObjectOutputStream(byteStream);

            os.writeObject(className);
            os.writeObject(methodName);
            os.writeObject(args);
            os.flush();

            conn.setDoOutput(true);
            conn.setUseCaches(false);
            conn.setRequestProperty("Content-Length", String.valueOf(byteStream.size()));
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

            byteStream.writeTo(conn.getOutputStream());

            in = conn.getInputStream();
            is = new ObjectInputStream(in);

            Serializable result = (Serializable) is.readObject();
            if (result instanceof RemoteInvocationException) {
            	throw (RemoteInvocationException) result;
            }
            return result;


        } catch (Exception ex) {
        	ex.printStackTrace();
        	return null;
        } finally {
        	try {
	        	is.close();
		        in.close();
		        os.close();
		        byteStream.close();
        	} catch (IOException ioex) {
        		ioex.printStackTrace();
        	}
        }
	}

	/**
	 * This method is used if the client wants to send a RemoteObject to
	 * the server so that operations on it are carried out on the server.
	 *
	 * @param targetUrl target URL
	 * @param ro remote object
	 * @return the modified RemoteObject
	 * @throws RemoteInvocationException
	 */
	public static Serializable invoke(String targetUrl, RemoteObject ro)
		throws RemoteInvocationException
	{
		return invoke(targetUrl, "", "", new Serializable[]{ro});
	}

	@SuppressWarnings("unchecked")
	public static <T> T getRemoteObject(Class<T> remoteInterface, String targetUrl, String className) {

		InvocationHandler handler =
			new RemoteInvocationHandler(className, targetUrl);

		T t = (T) Proxy.newProxyInstance(remoteInterface.getClassLoader(),
	            new Class[] { remoteInterface },
	            handler);

		return t;
	}
}

@SuppressWarnings("unchecked")
class RemoteInvocationHandler implements InvocationHandler {
	String targetUrl;
	String className;

	public RemoteInvocationHandler(String className, String targetUrl) {
		this.targetUrl = targetUrl;
		this.className = className;
	}

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {

		Serializable[] serializableArgs = new Serializable[args.length];
		try {
			for (int i = 0; i < args.length; i ++)
			serializableArgs[i] = (Serializable) args[i];
		} catch (Exception ex) {
			throw new Exception("All arguments must be Serializable");
		}

		return RMClient.invoke(targetUrl, className, method.getName(), serializableArgs);

	}
}